Some fixes to IRET hypercall and failsafe callback handlers:
authorkaf24@firebug.cl.cam.ac.uk <kaf24@firebug.cl.cam.ac.uk>
Wed, 29 Mar 2006 11:02:45 +0000 (12:02 +0100)
committerkaf24@firebug.cl.cam.ac.uk <kaf24@firebug.cl.cam.ac.uk>
Wed, 29 Mar 2006 11:02:45 +0000 (12:02 +0100)
 1. IRET hypercall must restore the event callback mask. This
    was missing on x86/64, and both subarchitectures now restore
    from EFLAGS.IF.
 2. Failsafe callbacks are fixed to detect whether fault is due to
    bad segment or due to IRET. In the latter case we now
    immediatiately kill the process. This avoids infinite looping
    between IRET hypercall and failsafe callback handler in the guest.

Signed-off-by: Keir Fraser <keir@xensource.com>
linux-2.6-xen-sparse/arch/i386/kernel/entry-xen.S
linux-2.6-xen-sparse/arch/x86_64/kernel/entry-xen.S
linux-2.6-xen-sparse/arch/x86_64/kernel/xen_entry.S
xen/arch/x86/x86_32/asm-offsets.c
xen/arch/x86/x86_32/traps.c
xen/arch/x86/x86_64/asm-offsets.c
xen/arch/x86/x86_64/traps.c

index 3d05727fdf67a00f3d4ac23b3a09e7003918bf02..c1784979e3a18b0a325035995295cbc6bb0bf43e 100644 (file)
@@ -65,7 +65,6 @@ ES            = 0x20
 ORIG_EAX       = 0x24
 EIP            = 0x28
 CS             = 0x2C
-EVENT_MASK     = 0x2E
 EFLAGS         = 0x30
 OLDESP         = 0x34
 OLDSS          = 0x38
@@ -290,14 +289,14 @@ restore_all:
 restore_nocheck:
 #else
 restore_nocheck:
-       testl $(VM_MASK|NMI_MASK), EFLAGS(%esp)
+       movl EFLAGS(%esp), %eax
+       testl $(VM_MASK|NMI_MASK), %eax
        jnz hypervisor_iret
-       movb EVENT_MASK(%esp), %al
-       notb %al                        # %al == ~saved_mask
+       shr $9, %eax                    # EAX[0] == IRET_EFLAGS.IF
        GET_VCPU_INFO
        andb evtchn_upcall_mask(%esi),%al
-       andb $1,%al                     # %al == mask & ~saved_mask
-       jnz restore_all_enable_events   #     != 0 => reenable event delivery
+       andb $1,%al                     # EAX[0] == IRET_EFLAGS.IF & event_mask
+       jnz restore_all_enable_events   #        != 0 => enable event delivery
 #endif
        RESTORE_REGS
        addl $4, %esp
@@ -555,14 +554,9 @@ scrit:     /**** START OF CRITICAL REGION ****/
        RESTORE_REGS
        addl $4, %esp
 1:     iret
-.section .fixup,"ax"
-2:     pushl $0
-       pushl $do_iret_error
-       jmp error_code
-.previous
 .section __ex_table,"a"
        .align 4
-       .long 1b,2b
+       .long 1b,iret_exc
 .previous
 14:    __DISABLE_INTERRUPTS
        jmp  11b
@@ -614,30 +608,51 @@ critical_fixup_table:
        .byte 0x00,0x00                 # jmp  11b
 
 # Hypervisor uses this for application faults while it executes.
+# We get here for two reasons:
+#  1. Fault while reloading DS, ES, FS or GS
+#  2. Fault while executing IRET
+# Category 1 we fix up by reattempting the load, and zeroing the segment
+# register if the load fails.
+# Category 2 we fix up by jumping to do_iret_error. We cannot use the
+# normal Linux return path in this case because if we use the IRET hypercall
+# to pop the stack frame we end up in an infinite loop of failsafe callbacks.
+# We distinguish between categories by maintaining a status value in EAX.
 ENTRY(failsafe_callback)
-1:     popl %ds
-2:     popl %es
-3:     popl %fs
-4:     popl %gs
-       subl $4,%esp
+       pushl %eax
+       movl $1,%eax
+1:     mov 4(%esp),%ds
+2:     mov 8(%esp),%es
+3:     mov 12(%esp),%fs
+4:     mov 16(%esp),%gs
+       testl %eax,%eax
+       popl %eax
+       jz 5f
+       addl $16,%esp           # EAX != 0 => Category 2 (Bad IRET)
+       jmp iret_exc
+5:     addl $16,%esp           # EAX == 0 => Category 1 (Bad segment)
+       pushl $0
        SAVE_ALL
-       jmp  ret_from_exception
-.section .fixup,"ax";  \
-6:     movl $0,(%esp); \
-       jmp 1b;         \
-7:     movl $0,(%esp); \
-       jmp 2b;         \
-8:     movl $0,(%esp); \
-       jmp 3b;         \
-9:     movl $0,(%esp); \
-       jmp 4b;         \
-.previous;             \
-.section __ex_table,"a";\
-       .align 4;       \
-       .long 1b,6b;    \
-       .long 2b,7b;    \
-       .long 3b,8b;    \
-       .long 4b,9b;    \
+       jmp ret_from_exception
+.section .fixup,"ax";          \
+6:     xorl %eax,%eax;         \
+       movl %eax,4(%esp);      \
+       jmp 1b;                 \
+7:     xorl %eax,%eax;         \
+       movl %eax,8(%esp);      \
+       jmp 2b;                 \
+8:     xorl %eax,%eax;         \
+       movl %eax,12(%esp);     \
+       jmp 3b;                 \
+9:     xorl %eax,%eax;         \
+       movl %eax,16(%esp);     \
+       jmp 4b;                 \
+.previous;                     \
+.section __ex_table,"a";       \
+       .align 4;               \
+       .long 1b,6b;            \
+       .long 2b,7b;            \
+       .long 3b,8b;            \
+       .long 4b,9b;            \
 .previous
 #endif
 
index ea24701bb81ba7eef4b0e46165634894b162da54..604365dec3a3d696f935cb7483dd949a8e6ad706 100644 (file)
@@ -520,13 +520,13 @@ retint_check:
        CFI_REMEMBER_STATE
        jnz  retint_careful
 retint_restore_args:
-        movb EVENT_MASK-REST_SKIP(%rsp), %al
-        notb %al                       # %al == ~saved_mask
-        XEN_GET_VCPU_INFO(%rsi)
-        andb evtchn_upcall_mask(%rsi),%al
-       andb $1,%al                     # %al == mask & ~saved_mask
-       jnz restore_all_enable_events   # != 0 => reenable event delivery      
-        XEN_PUT_VCPU_INFO(%rsi)
+       movl EFLAGS-REST_SKIP(%rsp), %eax
+       shr $9, %eax                    # EAX[0] == IRET_EFLAGS.IF
+       XEN_GET_VCPU_INFO(%rsi)
+       andb evtchn_upcall_mask(%rsi),%al
+       andb $1,%al                     # EAX[0] == IRET_EFLAGS.IF & event_mask
+       jnz restore_all_enable_events   #        != 0 => enable event delivery
+       XEN_PUT_VCPU_INFO(%rsi)
                
        RESTORE_ARGS 0,8,0
        HYPERVISOR_IRET 0
@@ -803,12 +803,11 @@ ENTRY(hypervisor_callback)
 # So, on entry to the handler we detect whether we interrupted an
 # existing activation in its critical region -- if so, we pop the current
 # activation and restart the handler using the previous one.
-ENTRY(do_hypervisor_callback)   # do_hyperviosr_callback(struct *pt_regs)
+ENTRY(do_hypervisor_callback)   # do_hypervisor_callback(struct *pt_regs)
 # Since we don't modify %rdi, evtchn_do_upall(struct *pt_regs) will
 # see the correct pointer to the pt_regs
        movq %rdi, %rsp            # we don't return, adjust the stack frame
-11:    movb $0, EVENT_MASK(%rdi)
-       movq %gs:pda_irqstackptr,%rax
+11:    movq %gs:pda_irqstackptr,%rax
        incl %gs:pda_irqcount
        cmovzq %rax,%rsp
        pushq %rdi
@@ -853,15 +852,44 @@ ecrit:  /**** END OF CRITICAL REGION ****/
 # When the kernel is interrupted in the critical section, the kernel 
 # will do IRET in that case, and everything will be restored at that point, 
 # i.e. it just resumes from the next instruction interrupted with the same context. 
-       
+
 # Hypervisor uses this for application faults while it executes.
-# Unlike i386 there is no need to reload the saved segment selectors:
-# Xen already reloaded all valid ones and zeroed the others.
+# We get here for two reasons:
+#  1. Fault while reloading DS, ES, FS or GS
+#  2. Fault while executing IRET
+# Category 1 we do not need to fix up as Xen has already reloaded all segment
+# registers that could be reloaded and zeroed the others.
+# Category 2 we fix up by killing the current process. We cannot use the
+# normal Linux return path in this case because if we use the IRET hypercall
+# to pop the stack frame we end up in an infinite loop of failsafe callbacks.
+# We distinguish between categories by comparing each saved segment register
+# with its current contents: any discrepancy means we in category 1.
 ENTRY(failsafe_callback)
-       addq $0x30,%rsp /* skip %rcx,%r11,%ds,%es,%fs,%gs */
+       movw %ds,%cx
+       cmpw %cx,0x10(%rsp)
+       jne 1f
+       movw %es,%cx
+       cmpw %cx,0x18(%rsp)
+       jne 1f
+       movw %fs,%cx
+       cmpw %cx,0x20(%rsp)
+       jne 1f
+       movw %gs,%cx
+       cmpw %cx,0x28(%rsp)
+       jne 1f
+       /* All segments match their saved values => Category 2 (Bad IRET). */
+       movq (%rsp),%rcx
+       movq 8(%rsp),%r11
+       addq $0x30,%rsp
+       movq $-9999,%rdi        /* better code? */
+       jmp do_exit                     
+1:     /* Segment mismatch => Category 1 (Bad segment). Retry the IRET. */
+       movq (%rsp),%rcx
+       movq 8(%rsp),%r11
+       addq $0x30,%rsp
+       pushq $0
        SAVE_ALL
-       jmp  error_exit
+       jmp error_exit
 #if 0        
         .section __ex_table,"a"
         .align 8
index 9b42d155663715ee078effed09ae8a52c1f6e927..b3d7f1940554199bf17f9a5478993ae25e8f0eb6 100644 (file)
@@ -2,7 +2,7 @@
  * Copied from arch/xen/i386/kernel/entry.S
  */                        
 /* Offsets into shared_info_t. */                
-#define evtchn_upcall_pending          0
+#define evtchn_upcall_pending          /* 0 */
 #define evtchn_upcall_mask             1
 
 #define sizeof_vcpu_shift              6
@@ -35,7 +35,6 @@
                                XEN_PUT_VCPU_INFO(reg)
 #define XEN_TEST_PENDING(reg)  testb $0xFF,evtchn_upcall_pending(reg)
 
-EVENT_MASK      = (CS+4)
 VGCF_IN_SYSCALL = (1<<8)
         
        
index 960a52cb74c9c1b64aee7742afba76a3c6c0de03..07830746beb3d2035593e4bf1f52feb9b6d7fe9a 100644 (file)
@@ -44,7 +44,6 @@ void __dummy__(void)
     OFFSET(UREGS_eflags, struct cpu_user_regs, eflags);
     OFFSET(UREGS_error_code, struct cpu_user_regs, error_code);
     OFFSET(UREGS_entry_vector, struct cpu_user_regs, entry_vector);
-    OFFSET(UREGS_saved_upcall_mask, struct cpu_user_regs, saved_upcall_mask);
     OFFSET(UREGS_kernel_sizeof, struct cpu_user_regs, esp);
     DEFINE(UREGS_user_sizeof, sizeof(struct cpu_user_regs));
     BLANK();
index 1c5daca90dab97ed01d1d6d8e0c0e7074ba99cb5..59d1a845a850356c1a93c8ff5c5af4f0e75d52be 100644 (file)
@@ -230,8 +230,8 @@ unsigned long do_iret(void)
     /* No longer in NMI context. */
     clear_bit(_VCPUF_nmi_masked, &current->vcpu_flags);
 
-    /* Restore upcall mask from saved value. */
-    current->vcpu_info->evtchn_upcall_mask = regs->saved_upcall_mask;
+    /* Restore upcall mask from supplied EFLAGS.IF. */
+    current->vcpu_info->evtchn_upcall_mask = !(eflags & X86_EFLAGS_IF);
 
     /*
      * The hypercall exit path will overwrite EAX with this return
index 91e9e810278ca96acfad92784a1345559677a4cc..ebc3059a9cf242ad742525a6c4169b26bb9157b9 100644 (file)
@@ -48,7 +48,6 @@ void __dummy__(void)
     OFFSET(UREGS_eflags, struct cpu_user_regs, eflags);
     OFFSET(UREGS_rsp, struct cpu_user_regs, rsp);
     OFFSET(UREGS_ss, struct cpu_user_regs, ss);
-    OFFSET(UREGS_saved_upcall_mask, struct cpu_user_regs, saved_upcall_mask);
     OFFSET(UREGS_kernel_sizeof, struct cpu_user_regs, es);
     DEFINE(UREGS_user_sizeof, sizeof(struct cpu_user_regs));
     BLANK();
index 46835570c4179ffe02956f50080ce6d2c35676fa..6794cefecf5107fa95c80f3672adf10bb56e1ee0 100644 (file)
@@ -210,6 +210,9 @@ unsigned long do_iret(void)
     /* No longer in NMI context. */
     clear_bit(_VCPUF_nmi_masked, &current->vcpu_flags);
 
+    /* Restore upcall mask from supplied EFLAGS.IF. */
+    current->vcpu_info->evtchn_upcall_mask = !(iret_saved.rflags & EF_IE);
+
     /* Saved %rax gets written back to regs->rax in entry.S. */
     return iret_saved.rax;
 }